﻿using System;
using System.Collections.Generic;
using System.Linq;
using CardPlay.Model.CardModel;
using CardPlay.Model.Layout;
using CardPlay.Model.Primitives;

namespace CardPlay.Model.GameModel.Presentation
{
    public static class PresentationExtensions
    {
        #region Cues

        public static BaseCues SetDealCue(this BaseCues cues, CardGame cardGame)
        {
            cues.AllowsRedeal = cardGame.IsRedealPossible;
            return cues;
        }

        public static BaseCues SetRankCue(this BaseCues cues, Rank? rank)
        {
            cues.Rank = rank;
            return cues;
        }

        public static BaseCues SetFoundationSuitCue(this BaseCues cues, CardGame cardGame, int startIndex, int count = 4)
        {
            return cues.SetFoundationSuitCue(cardGame, StandardPileNames.RangePatternForFoundations(startIndex, count));
        }

        public static BaseCues SetFoundationSuitCue(this BaseCues cues, CardGame cardGame, string pattern)
        {
            return cues.SetFoundationSuitCue(cardGame.NonEmptyPilesNamed(pattern));
        }

        public static BaseCues SetFoundationSuitCue(this BaseCues cues, IEnumerable<CardPile> foundations)
        {
            return cues.SetFoundationSuitCue(foundations, CardFace.AllSuits);
        }

        public static BaseCues SetFoundationSuitCue(this BaseCues cues, IEnumerable<CardPile> foundations, Suit[] candidateSuits)
        {
            cues.Suits = candidateSuits.Except(foundations.Select(pile => pile.BottomCard.Suit)).ToArray();
            return cues;
        }

        public static BaseCues SetRepeatedRankFoundationSuitCue(this BaseCues cues, IEnumerable<CardPile> setOfFoundations, Rank rank, int maxUsage)
        {
            return cues.SetRepeatedFoundationSuitCue(setOfFoundations, face => rank == face.Rank, maxUsage);
        }

        public static BaseCues SetRepeatedFoundationSuitCue(this BaseCues cues, IEnumerable<CardPile> setOfFoundations, Predicate<CardFace> filter, int maxUsage)
        {
            var unavailableSuits = setOfFoundations
                .Where(pile => !pile.IsEmpty && filter(pile.BottomCard.Face))
                .Select(pile => pile.BottomCard.Face)
                    .GroupBy(face => face.Suit)
                    .Where(grouping => grouping.Count() == maxUsage)
                    .Select(grouping => grouping.First().Suit);

            cues.Suits = CardFace.AllSuits.Except(unavailableSuits).ToArray();
            return cues;
        }

        #endregion

        public static Size MinimumWasteSize(this IPresentationContext context)
        {
            return new Size(context.Metrics.CardSize.Width * 3 / 2, context.Metrics.CardSize.Height * 7 / 6); 
        }

        public static VisualTraits HorizontalStackingStyle(this IPresentationContext context)
        {
            return new VisualTraits
            {
                Arrangement = CardArrangement.StackLeftToRight,
                MinimumSize = new Size(0, context.Metrics.CardSize.Height),
                VerticalAlignment = VerticalAlignment.Top,
                ContentFit = ContentFit.FitBounds
            };
        }

        public static VisualTraits VerticalStackingStyle(this IPresentationContext context)
        {
            return new VisualTraits
            {
                Arrangement = CardArrangement.StackTopToBottom,
                MinimumSize = new Size(context.Metrics.CardSize.Width, 0),
                VerticalAlignment = VerticalAlignment.Top,
                ContentFit = ContentFit.FitBounds
            };
        }

        public static VisualTraits StyleForWaste(this IPresentationContext context)
        {
            var style = StandardStyles.Waste;
            style.MinimumSize = context.MinimumWasteSize();

            return style;
        }

        public static VisualTraits StyleForStock(this IPresentationContext context, CardGame cardGame)
        {
            var style = StandardStyles.Stock;
            style.MinimumSize = new Size(context.Metrics.CardSize.Width * 5 / 4, context.Metrics.CardSize.Height * 9 / 8);
            style.EmptyCue = () => cardGame.IsRedealPossible ? EmptyPileCue.Redeal : EmptyPileCue.NoRedeal;

            return style;
        }

        public static VisualTraits StyleForRepeatedRankFoundation(this IPresentationContext context, CardGame cardGame, Rank rank, int maxUsage)
        {
            var style = StandardStyles.Foundation;
            style.UpdateBaseCues = (pile, cues) => cues.SetRankCue(rank).SetRepeatedRankFoundationSuitCue(cardGame.NonEmptyPilesNamed(CardGame.AnyFoundation), rank, maxUsage);

            return style;
        }

        public static LayoutArea CreateMimimalSpacer(this IPresentationContext context)
        {
            return context.CreateSpacer(context.Metrics.MinSpacingAcross, context.Metrics.MaxSpacingDown);
        }

        public static LayoutArea CreateCardSpacer(this IPresentationContext context)
        {
            return context.CreateSpacer(context.Metrics.CardSize.Width, context.Metrics.CardSize.Height);
        }

        public static LayoutArea CreateSpacer(this IPresentationContext context, int size)
        {
            return context.CreateSpacer(size, size);
        }

        public static LayoutArea CreateSpacer(this IPresentationContext context, int width, int height)
        {
            return new LayoutArea { Margin = new MarginSize(width, height, 0, 0) };
        }

        public static GridLayout CreateGridLayout(this IPresentationContext context, int columns, int rows)
        {
            return new GridLayout { Columns = columns, Rows = rows };
        }

        public static SideBySideLayout CreateRowLayout(this IPresentationContext context, HorizontalAlignment horizontalAlignment = HorizontalAlignment.Stretch)
        {
            var layout = new SideBySideLayout
            {
                Direction = SpreadDirection.Horizontal,
                MinimumSpacing = context.Metrics.MinSpacingAcross,
                MaximumSpacing = context.Metrics.MaxSpacingAcross,
                HorizontalContentAlignment = horizontalAlignment,
                VerticalContentAlignment = VerticalAlignment.Top
            };

            return layout;
        }

        public static SideBySideLayout CreateColumnLayout(this IPresentationContext context, VerticalAlignment verticalAlignment = VerticalAlignment.Stretch)
        {
            var layout = new SideBySideLayout
            {
                Direction = SpreadDirection.Vertical,
                MinimumSpacing = context.Metrics.MinSpacingDown,
                MaximumSpacing = context.Metrics.MaxSpacingDown,
                HorizontalContentAlignment = HorizontalAlignment.Left,
                VerticalContentAlignment = verticalAlignment
            };

            return layout;
        }

        public static SideBySideLayout CreateStockWasteLayout(this IPresentationContext context, CardGame cardGame, SpreadDirection direction = SpreadDirection.Horizontal)
        {
            var layout = context.CreateRowLayout();
            layout.Direction = direction;

            layout.Add(context.CreatePileVisual(CardGame.Stock, context.StyleForStock(cardGame)));
            layout.Add(context.CreatePileVisual(CardGame.Waste, context.StyleForWaste()));

            return layout;
        }

        public static CompositeLayout AddFoundationPiles(this IPresentationContext context, CardGame cardGame, CompositeLayout layout, Rank? rank)
        {
            var style = StandardStyles.Foundation;
            style.UpdateBaseCues = (pile, cues) => cues.SetRankCue(rank).SetFoundationSuitCue(cardGame, CardGame.AnyFoundation);

            return context.AddPiles(layout, cardGame.PilesNamed(CardGame.AnyFoundation), style);
        }

        public static CompositeLayout AddTableauPiles(this IPresentationContext context, CardGame cardGame, CompositeLayout layout)
        {
            return context.AddPiles(layout, cardGame.PilesNamed(CardGame.AnyTableau), StandardStyles.Tableau);
        }

        public static CompositeLayout AddPiles(this IPresentationContext context, CompositeLayout layout, IEnumerable<CardPile> pileList, VisualTraits traits)
        {
            return context.AddPiles(layout, pileList.Select(pile => pile.Name), traits);
        }

        public static CompositeLayout AddPiles(this IPresentationContext context, CompositeLayout layout, IEnumerable<string> pileList, VisualTraits traits)
        {
            return pileList.Select(name => context.CreatePileVisual(name, traits)).Aggregate(layout, (result, item) =>
            {
                result.Add(item);
                return result;
            });
        }
    }
}
